import { BodyItemConstant } from '../constants/InputItemConstants';
import proJSON from './masterdata/FiveHealthHabitProbabilityMaster.json';
import CommonUtils from './CommonUtils';

const { GENDER, SMOKING_HABIT, ALCOHOL_HABIT, ALCOHOL_FREQUENCY, SALINE_FOOD } = BodyItemConstant;
const GOOD = 1; // 良好（健康習慣の実践あり）
const BAD = 0;  // 不良（健康習慣の実践なし）

// 適正なBMIのしきい値(性別毎)
const MALE_OK_BMI_MIN = 21;
const MALE_OK_BMI_MAX = 27;
const FEMALE_OK_BMI_MIN = 19;
const FEMALE_OK_BMI_MAX = 25;

/**
 * 5つの健康習慣ロジック
 */
class FiveHealthHabitLogic {

  /**
   * 中間ちパラメータから、実施中かどうかのフラグを変換する。
   * @param {Object.<Object>} middleParameter 中間パラメータ
   * @return {Object} 診断結果（実施中の健康習慣5つ）
   */
  static toFiveHealtHabitFlgs(key) {
    const obj = {
      okSmoking: this._isPracticing(key.smoking),         // 非喫煙
      okAlcohol: this._isPracticing(key.alcohol),         // 節酒
      okBmi: this._isPracticing(key.bmi),                 // 適正なBMI値
      okActivity: this._isPracticing(key.activity),       // 活発な身体活動
      okSalineFood: this._isPracticing(key.salineFood),   // 塩蔵品を控える
      count: 0, // 以下で計算して代入する
    };

    /* 実践中の習慣を加算 */
    obj.count = [
      obj.okSmoking,
      obj.okActivity,
      obj.okBmi,
      obj.okSalineFood,
      obj.okAlcohol,
    ].reduce((cnt, flg) => flg ? cnt + 1 : cnt, 0);
    return obj;
  }

  /**
   * リスクを計算します。
   * @param {Object.<number>} inputData 入力値
   * @param {Object.<Object>} middleParameter 中間パラメータ
   * @return {Object.<Object>} がんになる危険度（自分、最大、最小）、診断結果（実施中の健康習慣5つ）
   */
  static calcRisk(inputData, { bmi, mets, ethanol, extractKey: key }, fiveHealtHabitFlgs) {
    // 抽出キー（自分、最悪、最良）
    if (fiveHealtHabitFlgs) {
      key = {...key};
      if (fiveHealtHabitFlgs.okSmoking) {
        key.smoking = GOOD;
      }
      if (fiveHealtHabitFlgs.okSalineFood) {
        key.salineFood = GOOD;
      }
      if (fiveHealtHabitFlgs.okBmi) {
        key.bmi = GOOD;
      }
      /* 節酒を実施中かつ、１日のアルコールの摂取量が、節酒と言える水準を下回っている場合 */
      if (fiveHealtHabitFlgs.okAlcohol && this._isPracticing(key.alcohol)) {
        key.alcohol = GOOD;
      } else {
        key.alcohol = BAD;
      }
      /* 活発な身体活動を実施中かつ、１日の運動量が、活発と言える水準を超えている場合 */
      if (fiveHealtHabitFlgs.okActivity && this._isPracticing(key.activity)) {
        key.activity = GOOD;
      } else {
        key.activity = BAD;
      }
    }

    const youKeys = [key.gender, key.age, key.smoking, key.alcohol, key.bmi, key.activity, key.salineFood];
    const worstKeys = [key.gender, key.age, BAD, BAD, BAD, BAD, BAD];
    const bestKeys = [key.gender, key.age, GOOD, GOOD, GOOD, GOOD, GOOD];
    const toFiveHealtHabitFlgs = this.toFiveHealtHabitFlgs(key);
    return {
      // がんになる危険度（確率）
      probability: {
        you: this._getProbability(youKeys),                 // あなた
        max: this._getProbability(worstKeys),               // 健康習慣を全く実施しないグループ
        min: this._getProbability(bestKeys),                // 健康習慣をすべて実施するグループ
      },
      diagnosis: {
        ...toFiveHealtHabitFlgs,
        okWeightRange: this._getOkWeightRange(inputData.gender, inputData.bodyHeight), // 適正な体重範囲
      },
    };
  }

  /**
   * アドバイスを生成します。
   * @param {Object.<number>} inputData 入力値
   * @param {Object.<Object>} middleParameter 中間パラメータ
   * @return {Object.<string>} アドバイス文
   */
  static takeAdvices({ gender }, { extractKey: key }) {
    // 健康習慣の実践（良好）数
    const practiceCount = key.smoking + key.alcohol + key.salineFood + key.activity + key.bmi;

    // アドバイス生成
    let advice = '';
    switch (practiceCount) {
      case 0:
        advice = '5つの健康習慣を、あなたは1つも実践できていないようです。５つの習慣はがん予防の基本です。取り入れ易いものから始めて、1つでも多く実践できるよう、生活の見直しが、いますぐ必要です！';
        break;
      case 1:
        advice = '5つの健康習慣のうち、あなたは1つを実践できていますね。<br/>しかし、がん予防の観点からは望ましい状況ではありません。実践し易いものを検討し、すぐに着手しましょう。';
        break;
      case 2:
        advice = '5つの健康習慣のうち、あなたは2つを実践できていますね。<br/>しかし、2つではがん予防の観点からは十分とは言えません。実践数が増えれば、まだまだリスクを減らせます。';
        break;
      case 3:
        if (gender === GENDER.MALE.value) {
          advice = '5つの健康習慣のうち、あなたは3つを実践できていますね。<br/>男性で最も多いのが３つ実践している人です。より健康を目指すなら、１つでも多く実践しましょう。';
        } else {
          advice = '5つの健康習慣のうち、あなたは3つを実践できていますね。<br/>健闘していますが、多くの人は４つの健康習慣を実践していますよ。もうひと頑張りです。';
        }
        break;
      case 4:
        if (gender === GENDER.MALE.value) {
          advice = '5つの健康習慣のうち、あなたは4つを実践できていますね。<br/>健闘していますね。この習慣を維持できるといいですね。';
        } else {
          advice = '5つの健康習慣のうち、あなたは4つを実践できていますね。<br/>女性で最も多いのが４つ実践している人です。この習慣を維持できるといいですね。';
        }
        break;
      case 5:
        advice = '5つの健康習慣のうち、あなたは5つを実践できていますね。<br/>すばらしいですね！加齢とともにがんのリスクは上がるもの。この習慣を維持できるといいですね。';
        break;
      default:
        throw new Error(`<5つの健康習慣> 想定外のデータ: 健康習慣の実践（良好）数=${practiceCount}`);
    }

    const result = {
      message: advice,
    };

    return result;
  }

  /**
   * 中間パラメータを生成します。
   * @param {Object.<number>} inputData 入力値
   * @param {Object.<number>} commonData 共通パラメータ
   * @return {Object.<Object>} 中間パラメータ、抽出キー
   */
  static calcMiddleParameter({ gender, age, smokingHabit, alcoholHabit, alcoholFrequency, salineFood }, { bmi, mets, ethanol }) {
    // 抽出キー生成
    const genKey = this._convExtractKeyFromGender(gender);                                    //   1桁目：性別
    const ageKey = this._convExtractKeyFromAge(age);                                          // 2,3桁目：年齢
    const smoKey = this._convExtractKeyFromSmokingHabit(smokingHabit);                        //   4桁目：喫煙
    const alcKey = this._convExtractKeyFromEthanol(alcoholHabit, alcoholFrequency, ethanol);  //   5桁目：飲酒
    const bmiKey = this._convExtractKeyFromBmi(gender, bmi);                                  //   6桁目：BMI
    const metKey = this._convExtractKeyFromMets(gender, mets);                                //   7桁目：METs
    const salKey = this._convExtractKeyFromSalineFood(salineFood);                            //   8桁目：塩蔵品

    // 中間パラメータ
    const param = {
      bmi,                    // BMI
      mets,                   // メッツ
      ethanol,                // エタノール量（g）
      extractKey: {
        gender: genKey,       //   1桁目：性別
        age: ageKey,          // 2,3桁目：年齢
        smoking: smoKey,      //   4桁目：喫煙
        alcohol: alcKey,      //   5桁目：飲酒
        bmi: bmiKey,          //   6桁目：BMI
        activity: metKey,     //   7桁目：METs
        salineFood: salKey,   //   8桁目：塩蔵品
      },
    };

    return param;
  }

  /**
   * マスタからがんになる危険度（確率）を引き当てます。
   * @param {Array.<(string|number)>} keys 抽出キー配列｛性別, 年齢, 喫煙, 飲酒, BMI, METs, 塩蔵品｝
   * @return リスク値（％）
   */
  static _getProbability(keys) {
    // マスタ上で小数第3位以下が四捨五入されている
    const risk = Number(proJSON[keys.join('')]);
    return risk;
  }

  /**
   * 実施済みかどうかを返します。
   * @param {number} key 抽出キー（0 or 1）
   * @return {boolean} true:実施あり, false:実施なし
   */
  static _isPracticing(key) {
    return key === GOOD;
  }

  /**
   * 抽出キー（性別）に変換します。
   * @param {number} gender 性別
   * @return {string} 'M':男性, 'W':女性
   */
  static _convExtractKeyFromGender(gender) {
    switch (gender) {
      case GENDER.MALE.value:
        return 'M';
      case GENDER.FEMALE.value:
        return 'W';
      default: break;
    }
    throw new Error(`<5つの健康習慣> 想定外のデータ: 性別=${gender}`);
  }

  /**
   * 抽出キー（年齢）に変換します。
   * @param {number} age 年齢2桁
   * @return {string} 年齢2桁
   */
  static _convExtractKeyFromAge(age) {
    if (age < 45) {
      // 45歳未満は45歳に補正
      age = 45;
    } else if (age > 74) {
      // 74歳超は74歳に補正
      age = 74;
    }
    return String(age);
  }

  /**
   * 抽出キー（喫煙）に変換します。
   * @param {number} smokingHabit 喫煙習慣
   * @return {number} 0:喫煙あり, 1:喫煙なし
   */
  static _convExtractKeyFromSmokingHabit(smokingHabit) {
    switch (smokingHabit) {
      case SMOKING_HABIT.IS_SMOKING.value:          // 吸っている
      case SMOKING_HABIT.STOPPED_SHORT_AGO.value:   // やめた(禁煙期間20年未満)
        return BAD;
      case SMOKING_HABIT.STOPPED_LONG_AGO.value:    // やめた(禁煙期間20年以上)
      case SMOKING_HABIT.NO_SMOKING.value:          // 吸わない
        return GOOD;
      default: break;
    }
    throw new Error(`<5つの健康習慣> 想定外のデータ: 喫煙習慣=${smokingHabit}`);
  }

  /**
   * 抽出キー（飲酒）に変換します。
   * @param {number} alcoholHabit 飲酒習慣
   * @param {number} alcoholFrequency 飲酒頻度
   * @param {number} ethanol 飲酒量（エタノール摂取量【g】）
   * @return {number} 0:節酒なし, 1:節酒あり
   */
  static _convExtractKeyFromEthanol(alcoholHabit, alcoholFrequency, ethanol) {
    if (alcoholHabit === ALCOHOL_HABIT.IS_DRINKING.value) {
      switch (alcoholFrequency) {
        case ALCOHOL_FREQUENCY.WEEK_LESS2.value:        // 週1～2日
        case ALCOHOL_FREQUENCY.WEEK_LESS4.value:        // 週3～4日
        case ALCOHOL_FREQUENCY.ALMOST_EVERY_DAY.value:  // 週5～6日
        case ALCOHOL_FREQUENCY.EVERY_DAY.value:         // 毎日
          return (ethanol >= 150) ? BAD : GOOD;
        default: break;
      }
    }
    return GOOD;
  }

  /**
   * 抽出キー（BMI）に変換します。
   * @param {number} gender 性別
   * @param {number} bmi BMI
   * @return {number} 0:不適正なBMI値, 1:適正なBMI値
   */
  static _convExtractKeyFromBmi(gender, bmi) {
    switch (gender) {
      case GENDER.MALE.value:
        return (MALE_OK_BMI_MIN <= bmi && bmi < MALE_OK_BMI_MAX) ? GOOD : BAD;
      case GENDER.FEMALE.value:
        return (FEMALE_OK_BMI_MIN <= bmi && bmi < FEMALE_OK_BMI_MAX) ? GOOD : BAD;
      default: break;
    }
    throw new Error(`<5つの健康習慣> 想定外のデータ: 性別=${gender}`);
  }

  /**
   * 抽出キー（METs）に変換します。
   * @param {number} gender 性別
   * @param {number} mets METs
   * @return {number} 0:運動なし, 1:運動あり
   */
  static _convExtractKeyFromMets(gender, mets) {
    switch (gender) {
      case GENDER.MALE.value:
        return (mets >= 37.5) ? GOOD : BAD;
      case GENDER.FEMALE.value:
        return (mets >= 31.9) ? GOOD : BAD;
      default: break;
    }
    throw new Error(`<5つの健康習慣> 想定外のデータ: 性別=${gender}`);
  }

  /**
   * 抽出キー（塩蔵品）に変換します。
   * @param {number} salineFood 塩蔵品
   * @return {number} 0:塩蔵品あり, 1:塩蔵品なし
   */
  static _convExtractKeyFromSalineFood(salineFood) {
    switch (salineFood) {
      case SALINE_FOOD.NONE.value:              // まったく食べない
      case SALINE_FOOD.SOMETIMES.value:         // たまに食べる
        return GOOD;
      case SALINE_FOOD.WEEK_LESS2.value:        // 週に1～2日
      case SALINE_FOOD.WEEK_LESS4.value:        // 週に3～4日
      case SALINE_FOOD.ALMOST_EVERY_DAY.value:  // ほとんど毎日
        return BAD;
      default: break;
    }
    throw new Error(`<5つの健康習慣> 想定外のデータ: 塩蔵品=${salineFood}`);
  }

  /**
   * 身長から適正な範囲のBMIに対する体重の体重を取得
   * @param {number} gender 性別
   * @param {number} bodyHeight 身長（cm）
   * @return {Object.number} 体重の範囲(kg)
   */
  static _getOkWeightRange(gender, bodyHeight) {
    return {
      minWeight: CommonUtils.calcWeightFromBMI(bodyHeight, gender === GENDER.MALE.value ? MALE_OK_BMI_MIN : FEMALE_OK_BMI_MIN),
      maxWeight: CommonUtils.calcWeightFromBMI(bodyHeight, gender === GENDER.MALE.value ? MALE_OK_BMI_MAX : FEMALE_OK_BMI_MAX),
    };
  }

}

export default FiveHealthHabitLogic
